<!doctype html><html lang=zh-hans>
<head>
<meta charset=utf-8>
<meta name=viewport content="width=device-width,initial-scale=1">
<meta http-equiv=x-ua-compatible content="IE=edge">
<meta name=author content="小李刀刀">
<meta name=description content="开发者可以使用 spiral/validation 组件来进行数据验证。该组件提供了基于数组的 DSL 来构建复杂验证链的功能。 该组件包含检查器、条件判断和验证对象。Web 和 GRPC 应用程序模板已经模板包含了这个组件。 对于深层数据验证，请参考 请求验证 文档。 安装和配置 要自行安装该组件，可以执行： $ composer install spiral/validation 要在 Spiral 框架的应用程序中使用，">
<meta name=theme-color content="#3f51b5">
<link rel=stylesheet href=https://cdnjs.cloudflare.com/ajax/libs/academicons/1.8.6/css/academicons.min.css integrity="sha256-uFVgMKfistnJAfoCUQigIl+JfUaP47GrRKjf6CTPVmw=" crossorigin=anonymous>
<link rel=stylesheet href=https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.0-1/css/all.min.css integrity="sha256-4w9DunooKSr3MFXHXWyFER38WmPdm361bQS/2KUWZbU=" crossorigin=anonymous>
<link rel=stylesheet href=https://cdnjs.cloudflare.com/ajax/libs/fancybox/3.5.7/jquery.fancybox.min.css integrity="sha256-Vzbj7sDDS/woiFS3uNKo8eIuni59rjyNGtXfstRzStA=" crossorigin=anonymous>
<link rel=stylesheet href=https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.1/styles/tomorrow-night.min.css crossorigin=anonymous title=hl-light>
<link rel=stylesheet href=https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.1/styles/tomorrow-night.min.css crossorigin=anonymous title=hl-dark disabled>
<script src=https://cdnjs.cloudflare.com/ajax/libs/lazysizes/5.1.2/lazysizes.min.js integrity="sha256-Md1qLToewPeKjfAHU1zyPwOutccPAm5tahnaw7Osw0A=" crossorigin=anonymous async></script>
<link rel=stylesheet href="https://fonts.font.im/css?family=Source+Code+Pro:300,400,500,600,700&subset=latin-ext&display=swap">
<link rel=stylesheet href=/css/academic.css>
<script>(function(b,d,e,a,g){b[a]=b[a]||[],b[a].push({'gtm.start':(new Date).getTime(),event:'gtm.js'});var f=d.getElementsByTagName(e)[0],c=d.createElement(e),h=a!='dataLayer'?'&l='+a:'';c.async=!0,c.src='https://www.googletagmanager.com/gtm.js?id='+g+h,f.parentNode.insertBefore(c,f)})(window,document,'script','dataLayer','GTM-5KZH8N7')</script>
<link rel=manifest href=/index.webmanifest>
<link rel=icon type=image/png href=/images/icon_hu0b7a4cb9992c9ac0e91bd28ffd38dd00_9727_32x32_fill_lanczos_center_3.png>
<link rel=apple-touch-icon type=image/png href=/images/icon_hu0b7a4cb9992c9ac0e91bd28ffd38dd00_9727_192x192_fill_lanczos_center_3.png>
<link rel=canonical href=https://studyspiral.cn/docs/security/validation/>
<meta property="twitter:card" content="summary">
<meta property="og:site_name" content="Spiral中文网">
<meta property="og:url" content="https://studyspiral.cn/docs/security/validation/">
<meta property="og:title" content="数据验证 | Spiral中文网">
<meta property="og:description" content="开发者可以使用 spiral/validation 组件来进行数据验证。该组件提供了基于数组的 DSL 来构建复杂验证链的功能。 该组件包含检查器、条件判断和验证对象。Web 和 GRPC 应用程序模板已经模板包含了这个组件。 对于深层数据验证，请参考 请求验证 文档。 安装和配置 要自行安装该组件，可以执行： $ composer install spiral/validation 要在 Spiral 框架的应用程序中使用，"><meta property="og:image" content="https://studyspiral.cn/images/logo.svg">
<meta property="twitter:image" content="https://studyspiral.cn/images/logo.svg"><meta property="og:locale" content="zh-Hans">
<meta property="article:published_time" content="2020-04-12T23:23:11+08:00">
<meta property="article:modified_time" content="2020-08-28T15:38:18+08:00">
<script src=https://cdnjs.cloudflare.com/ajax/libs/cookieconsent2/3.1.1/cookieconsent.min.js integrity="sha256-5VhCqFam2Cn+yjw61zbBNrbHVJ6SRydPeKopYlngbiQ=" crossorigin=anonymous></script>
<link rel=stylesheet href=https://cdnjs.cloudflare.com/ajax/libs/cookieconsent2/3.1.1/cookieconsent.min.css integrity="sha256-zQ0LblD/Af8vOppw18+2anxsuaz3pWYyVWi+bTvTH8Q=" crossorigin=anonymous>
<script>window.addEventListener("load",function(){window.cookieconsent.initialise({palette:{popup:{background:"#3f51b5",text:"#fff"},button:{background:"#fff",text:"#3f51b5"}},theme:"classic",content:{message:"本网站使用cookies来确保您在本网站上获得最佳体验。",dismiss:"知道了!",link:"了解更多",href:"https://www.cookiesandyou.com"}})})</script>
<title>数据验证 | Spiral中文网</title>
</head>
<body id=top data-spy=scroll data-offset=70 data-target=#TableOfContents>
<aside class=search-results id=search>
<div class=container>
<section class=search-header>
<div class="row no-gutters justify-content-between mb-3">
<div class=col-6>
<h1>搜索</h1>
</div>
<div class="col-6 col-search-close">
<a class=js-search href=#><i class="fas fa-times-circle text-muted" aria-hidden=true></i></a>
</div>
</div>
<div id=search-box>
<input name=q id=search-query placeholder=搜索... autocapitalize=off autocomplete=off autocorrect=off spellcheck=false type=search>
</div>
</section>
<section class=section-search-results>
<div id=search-hits>
</div>
</section>
</div>
</aside>
<nav class="navbar navbar-expand-lg navbar-light compensate-for-scrollbar" id=navbar-main>
<div class=container>
<div class="d-none d-lg-inline-flex">
<a class=navbar-brand href=/><img src=/images/logo.svg alt=Spiral中文网>Spiral中文网</a>
</div>
<button type=button class=navbar-toggler data-toggle=collapse data-target=#navbar-content aria-controls=navbar aria-expanded=false aria-label=切换导航>
<span><i class="fas fa-bars"></i></span>
</button>
<div class="navbar-brand-mobile-wrapper d-inline-flex d-lg-none">
<a class=navbar-brand href=/><img src=/images/logo.svg alt=Spiral中文网>Spiral中文网</a>
</div>
<div class="navbar-collapse main-menu-item collapse justify-content-end" id=navbar-content>
<ul class="navbar-nav d-md-inline-flex">
<li class=nav-item>
<a class=nav-link href=/docs/basics/quick-start/><span>教程</span></a>
</li>
<li class=nav-item>
<a class="nav-link active" href=/docs/><span>文档</span></a>
</li>
<li class=nav-item>
<a class=nav-link href=/post/><span>文章</span></a>
</li>
</ul>
</div>
<ul class="nav-icons navbar-nav flex-row ml-auto d-flex pl-md-2">
<li class=nav-item>
<a class="nav-link js-search" href=#><i class="fas fa-search" aria-hidden=true></i></a>
</li>
</ul>
</div>
</nav>
<div class="container-fluid docs">
<div class="row flex-xl-nowrap">
<div class="col-12 col-md-3 col-xl-2 docs-sidebar">
<form class="docs-search d-flex align-items-center">
<button class="btn docs-toggle d-md-none p-0 mr-3" type=button data-toggle=collapse data-target=#docs-nav aria-controls=docs-nav aria-expanded=false aria-label="Toggle section navigation">
<span><i class="fas fa-bars"></i></span>
</button>
<input name=q type=search class=form-control placeholder=搜索... autocomplete=off>
</form>
<nav class="collapse docs-links" id=docs-nav>
<div class=docs-toc-item>
<a class=docs-toc-link href=/docs/extension/dotenv/>Dotenv</a>
</div>
<div class=docs-toc-item>
<a class=docs-toc-link href=/docs/>总览</a>
<ul class="nav docs-sidenav docs-sidenav-sub collapse show">
<li>
<a href=/docs/>Spiral介绍</a>
</li>
<li>
<a href=/docs/about/contributing/>贡献指引</a>
</li>
<li>
<a href=/docs/about/semver/>版本说明</a>
</li>
<li>
<a href=/docs/about/license/>授权协议</a>
</li>
</ul>
</div>
<div class=docs-toc-item>
<a class=docs-toc-link href=/docs/http/psr-15/>定制 PSR-15 处理器</a>
</div>
<div class=docs-toc-item>
<a class=docs-toc-link href=/docs/start/install/>快速开始</a>
<ul class="nav docs-sidenav docs-sidenav-sub collapse">
<li>
<a href=/docs/start/install/>安装指引</a>
</li>
<li>
<a href=/docs/start/workers/>应用工作进程</a>
</li>
<li>
<a href=/docs/start/structure/>应用程序结构</a>
</li>
<li>
<a href=/docs/start/configuration/>默认配置</a>
</li>
<li>
<a href=/docs/start/commands/>控制台命令</a>
</li>
</ul>
</div>
<div class=docs-toc-item>
<a class=docs-toc-link href=/docs/basics/quick-start/>入门基础</a>
<ul class="nav docs-sidenav docs-sidenav-sub collapse">
<li>
<a href=/docs/basics/quick-start/>上手指南</a>
</li>
<li>
<a href=/docs/basics/scaffolding/>脚手架</a>
</li>
<li>
<a href=/docs/basics/prototype/>原型开发辅助</a>
</li>
</ul>
</div>
<div class=docs-toc-item>
<a class=docs-toc-link href=/docs/framework/design/>核心框架</a>
<ul class="nav docs-sidenav docs-sidenav-sub collapse">
<li>
<a href=/docs/framework/design/>设计理念</a>
</li>
<li>
<a href=/docs/framework/application-server/>应用服务器</a>
</li>
<li>
<a href=/docs/framework/config/>配置对象</a>
</li>
<li>
<a href=/docs/framework/kernel/>内核与环境</a>
</li>
<li>
<a href=/docs/framework/container/>容器</a>
</li>
<li>
<a href=/docs/framework/bootloaders/>引导程序</a>
</li>
<li>
<a href=/docs/framework/scopes/>IoC 作用域</a>
</li>
<li>
<a href=/docs/framework/memory/>静态高速缓存</a>
</li>
<li>
<a href=/docs/framework/finalizers/>终结器</a>
</li>
</ul>
</div>
<div class=docs-toc-item>
<a class=docs-toc-link href=/docs/cookbook/annotated-routes/>速查手册</a>
<ul class="nav docs-sidenav docs-sidenav-sub collapse">
<li>
<a href=/docs/cookbook/annotated-routes/>注解式路由</a>
</li>
<li>
<a href=/docs/cookbook/injector/>容器注入器</a>
</li>
<li>
<a href=/docs/cookbook/domain-core/>领域内核、控制器</a>
</li>
<li>
<a href=/docs/cookbook/golang-library/>Golang服务集成</a>
</li>
<li>
<a href=/docs/cookbook/custom-dispatcher/>自定义调度器</a>
</li>
</ul>
</div>
<div class=docs-toc-item>
<a class=docs-toc-link href=/docs/component/files/>常用组件</a>
<ul class="nav docs-sidenav docs-sidenav-sub collapse">
<li>
<a href=/docs/component/files/>文件和目录</a>
</li>
<li>
<a href=/docs/component/reactor/>代码生成</a>
</li>
<li>
<a href=/docs/component/tokenizer/>静态分析工具</a>
</li>
<li>
<a href=/docs/component/metrics/>应用指标</a>
</li>
<li>
<a href=/docs/component/data-grid/>数据网格</a>
</li>
</ul>
</div>
<div class=docs-toc-item>
<a class=docs-toc-link href=/docs/console/configuration/>控制台</a>
<ul class="nav docs-sidenav docs-sidenav-sub collapse">
<li>
<a href=/docs/console/configuration/>安装和配置</a>
</li>
<li>
<a href=/docs/console/commands/>用户命令</a>
</li>
</ul>
</div>
<div class=docs-toc-item>
<a class=docs-toc-link href=/docs/http/configuration/>HTTP</a>
<ul class="nav docs-sidenav docs-sidenav-sub collapse">
<li>
<a href=/docs/http/configuration/>安装配置</a>
</li>
<li>
<a href=/docs/http/lifecycle/>请求生命周期</a>
</li>
<li>
<a href=/docs/http/request-response/>请求和响应</a>
</li>
<li>
<a href=/docs/http/routing/>路由</a>
</li>
<li>
<a href=/docs/http/errors/>错误页面</a>
</li>
<li>
<a href=/docs/http/middleware/>中间件</a>
</li>
<li>
<a href=/docs/http/golang/>Golang 中间件</a>
</li>
<li>
<a href=/docs/http/cookies/>Cookies</a>
</li>
<li>
<a href=/docs/http/session/>Session</a>
</li>
<li>
<a href=/docs/http/csrf/>CSRF 防护</a>
</li>
</ul>
</div>
<div class=docs-toc-item>
<a class=docs-toc-link href=/docs/security/encrypter/>安全</a>
<ul class="nav docs-sidenav docs-sidenav-sub collapse show">
<li>
<a href=/docs/security/encrypter/>数据加密</a>
</li>
<li class=active>
<a href=/docs/security/validation/>数据验证</a>
</li>
<li>
<a href=/docs/security/rbac/>基于角色的权限控制</a>
</li>
<li>
<a href=/docs/security/authentication/>用户认证</a>
</li>
</ul>
</div>
<div class=docs-toc-item>
<a class=docs-toc-link href=/docs/filters/configuration/>请求过滤</a>
<ul class="nav docs-sidenav docs-sidenav-sub collapse">
<li>
<a href=/docs/filters/configuration/>安装和配置</a>
</li>
<li>
<a href=/docs/filters/filter/>过滤器</a>
</li>
<li>
<a href=/docs/filters/composite/>复合过滤器</a>
</li>
</ul>
</div>
<div class=docs-toc-item>
<a class=docs-toc-link href=/docs/database/configuration/>数据库</a>
<ul class="nav docs-sidenav docs-sidenav-sub collapse">
<li>
<a href=/docs/database/configuration/>安装与配置</a>
</li>
<li>
<a href=/docs/database/access/>访问数据</a>
</li>
<li>
<a href=/docs/database/isolation/>逻辑隔离</a>
</li>
<li>
<a href=/docs/database/query-builders/>查询构造器</a>
</li>
<li>
<a href=/docs/database/transactions/>Transactions</a>
</li>
<li>
<a href=/docs/database/introspection/>Schema Introspection</a>
</li>
<li>
<a href=/docs/database/declaration/>Declaration</a>
</li>
<li>
<a href=/docs/database/migrations/>Migrations</a>
</li>
<li>
<a href=/docs/database/errata/>Errata</a>
</li>
</ul>
</div>
<div class=docs-toc-item>
<a class=docs-toc-link href=/docs/cycle/configuration/>Cycle ORM</a>
<ul class="nav docs-sidenav docs-sidenav-sub collapse">
<li>
<a href=/docs/cycle/configuration/>Configuration</a>
</li>
</ul>
</div>
<div class=docs-toc-item>
<a class=docs-toc-link href=/docs/queue/configuration/>队列任务</a>
<ul class="nav docs-sidenav docs-sidenav-sub collapse">
<li>
<a href=/docs/queue/configuration/>Configuration</a>
</li>
<li>
<a href=/docs/queue/scraper/>网站爬虫</a>
</li>
</ul>
</div>
<div class=docs-toc-item>
<a class=docs-toc-link href=/docs/views/configuration/>视图</a>
<ul class="nav docs-sidenav docs-sidenav-sub collapse">
<li>
<a href=/docs/views/configuration/>Configuration</a>
</li>
</ul>
</div>
<div class=docs-toc-item>
<a class=docs-toc-link href=/docs/stempler/configuration/>Stempler 模板</a>
<ul class="nav docs-sidenav docs-sidenav-sub collapse">
<li>
<a href=/docs/stempler/configuration/>Configuration</a>
</li>
<li>
<a href=/docs/stempler/directives/>Directives</a>
</li>
</ul>
</div>
<div class=docs-toc-item>
<a class=docs-toc-link>国际化</a>
</div>
<div class=docs-toc-item>
<a class=docs-toc-link>GRPC</a>
</div>
<div class=docs-toc-item>
<a class=docs-toc-link>事件广播</a>
</div>
<div class=docs-toc-item>
<a class=docs-toc-link>调试及性能</a>
</div>
<div class=docs-toc-item>
<a class=docs-toc-link>扩展</a>
</div>
</nav>
</div>
<div class="d-none d-xl-block col-xl-2 docs-toc">
<ul class="nav toc-top">
<li><a href=# id=back_to_top class=docs-toc-title>在本页</a></li>
</ul>
<nav id=TableOfContents>
<ul>
<li><a href=#安装和配置>安装和配置</a></li>
<li><a href=#验证接口>验证接口</a>
<ul>
<li><a href=#有效数据>有效数据</a></li>
<li><a href=#错误格式>错误格式</a></li>
</ul>
</li>
<li><a href=#验证-dsl>验证 DSL</a>
<ul>
<li><a href=#扩展声明>扩展声明</a></li>
<li><a href=#检查器规则>检查器规则</a></li>
<li><a href=#参数>参数</a></li>
<li><a href=#错误信息>错误信息</a></li>
<li><a href=#判断条件>判断条件</a></li>
<li><a href=#可用的条件>可用的条件</a></li>
</ul>
</li>
<li><a href=#可用的规则>可用的规则</a>
<ul>
<li><a href=#规则别名>规则别名</a></li>
<li><a href=#类型>类型</a></li>
<li><a href=#必填项>必填项</a></li>
<li><a href=#混合>混合</a></li>
<li><a href=#地址>地址</a></li>
</ul>
</li>
<li><a href=#数字>数字</a>
<ul>
<li><a href=#字符串>字符串</a></li>
<li><a href=#文件>文件</a></li>
<li><a href=#图片>图片</a></li>
<li><a href=#日期时间>日期时间</a></li>
<li><a href=#实体>实体</a></li>
</ul>
</li>
<li><a href=#自定义验证规则>自定义验证规则</a></li>
</ul>
</nav>
</div>
<main class="col-12 col-md-9 col-xl-8 py-md-3 pl-md-5 docs-content" role=main>
<article class=article>
<div class="alert alert-warning" role=alert>
官方文档中文版翻译工作仍在进行中，欢迎 <a href=/post/join-translation/>参与翻译</a>。
</div>
<div class=docs-article-container>
<h1>数据验证</h1>
<div class=article-style>
<p>开发者可以使用 <code>spiral/validation</code> 组件来进行数据验证。该组件提供了基于数组的 DSL 来构建复杂验证链的功能。</p>
<p>该组件包含检查器、条件判断和验证对象。Web 和 GRPC 应用程序模板已经模板包含了这个组件。</p>
<blockquote>
<p>对于深层数据验证，请参考
<a href=/docs/filters/configuration/>请求验证</a> 文档。</p>
</blockquote>
<h2 id=安装和配置>安装和配置</h2>
<p>要自行安装该组件，可以执行：</p>
<pre><code class=language-bash>$ composer install spiral/validation
</code></pre>
<p>要在 Spiral 框架的应用程序中使用，需注册 <code>Spiral\Bootloader\Security\ValidationBootloader</code> 引导程序。</p>
<p>需要对组件的配置选项进行修改，可以创建和编辑 <code>app/config/validation.php</code> 文件。默认配置如下：</p>
<pre><code class=language-php>&lt;?php

declare(strict_types=1);

use Spiral\Validation;

return [
    // 检查器（checkers）会由 container 进行解析并提供在通用名称和类之下的
    // 彼此独立的验证规则。你可以根据需要注册新的检查器，注册检查器的数量不会带来性能问题。
    'checkers'   =&gt; [
        'type'    =&gt; Validation\Checker\TypeChecker::class,
        'number'  =&gt; Validation\Checker\NumberChecker::class,
        'mixed'   =&gt; Validation\Checker\MixedChecker::class,
        'address' =&gt; Validation\Checker\AddressChecker::class,
        'string'  =&gt; Validation\Checker\StringChecker::class,
        'file'    =&gt; Validation\Checker\FileChecker::class,
        'image'   =&gt; Validation\Checker\ImageChecker::class,
    ],

    // 启用/禁用 验证判断条件
    'conditions' =&gt; [
        'withAny'    =&gt; Validation\Condition\WithAnyCondition::class,
        'withoutAny' =&gt; Validation\Condition\WithoutAnyCondition::class,
        'withAll'    =&gt; Validation\Condition\WithAllCondition::class,
        'withoutAll' =&gt; Validation\Condition\WithoutAllCondition::class,
    ],

    // 别名只是为了让开发更简单
    'aliases'    =&gt; [
        'notEmpty'   =&gt; 'type::notEmpty',
        'required'   =&gt; 'type::notEmpty',
        'datetime'   =&gt; 'datetime::valid',
        'timezone'   =&gt; 'datetime::timezone',
        'bool'       =&gt; 'type::boolean',
        'boolean'    =&gt; 'type::boolean',
        'cardNumber' =&gt; 'mixed::cardNumber',
        'regexp'     =&gt; 'string::regexp',
        'email'      =&gt; 'address::email',
        'url'        =&gt; 'address::url',
        'file'       =&gt; 'file::exists',
        'uploaded'   =&gt; 'file::uploaded',
        'filesize'   =&gt; 'file::size',
        'image'      =&gt; 'image::valid',
        'array'      =&gt; 'is_array',
        'callable'   =&gt; 'is_callable',
        'double'     =&gt; 'is_double',
        'float'      =&gt; 'is_float',
        'int'        =&gt; 'is_int',
        'integer'    =&gt; 'is_integer',
        'numeric'    =&gt; 'is_numeric',
        'long'       =&gt; 'is_long',
        'null'       =&gt; 'is_null',
        'object'     =&gt; 'is_object',
        'real'       =&gt; 'is_real',
        'resource'   =&gt; 'is_resource',
        'scalar'     =&gt; 'is_scalar',
        'string'     =&gt; 'is_string',
        'match'      =&gt; 'mixed::match',
    ]
];
</code></pre>
<p>在开发中（通常在控制器或服务）可以通过提供者工厂使用此组件：</p>
<pre><code class=language-php>namespace App\Controller;

use Spiral\Validation;

class HomeController
{
    public function index(Validation\ValidationInterface $validation)
    {
        $validator = $validation-&gt;validate(
            // 要验证的数据
            [
                'key' =&gt; null
            ],
            // 验证规则
            [
                'key' =&gt; [
                    'notEmpty'
                ]
            ]
        );

        dump($validator instanceof Validation\Validator);

        dump($validator-&gt;isValid());
        dump($validator-&gt;withData(['key' =&gt; 'value'])-&gt;isValid());
    }
}
</code></pre>
<blockquote>
<p>也可以直接使用开发辅助原型的 <code>validator</code> 属性来使用验证组件。</p>
</blockquote>
<h2 id=验证接口>验证接口</h2>
<p><code>ValidationInterface</code>-><code>validate</code> 方法返回的结果是 <code>ValidatorInterface</code> 接口的实现。该接口提供了基本的 API 来获取验证结果和错误，并允许将其附加的新的数据或者不可变上下文：</p>
<pre><code class=language-php>interface ValidatorInterface
{
    public function withData($data): ValidatorInterface;
    public function getValue(string $field, $default = null);
    public function withContext($context): ValidatorInterface;
    public function getContext();
    public function isValid(): bool;
    public function getErrors(): array;
}
</code></pre>
<p>正常的流程是使用 <code>isValid</code>：</p>
<pre><code class=language-php>public function index(Validation\ValidationInterface $validation)
{
    $validator = $validation-&gt;validate(
        ['key' =&gt; null],
        ['key' =&gt; ['notEmpty']]
    );

    if (!$validator-&gt;isValid()) {
        dump($validator-&gt;getErrors());
    }
}
</code></pre>
<h3 id=有效数据>有效数据</h3>
<p>验证器接受任何数组数据源，但在内部它会把数据源转换为数组形式（除了 ArrayAccess）。</p>
<h3 id=错误格式>错误格式</h3>
<p>验证组件总会返回一个 error 对象，包含每个键触发的第一个验证错误。</p>
<pre><code class=language-php>[
    'name' =&gt; 'This field is required.',
    'key'  =&gt; 'Another error'
]
</code></pre>
<blockquote>
<p>错误信息可以使用 <code>spiral/translator</code> 组件进行本地化。</p>
</blockquote>
<h2 id=验证-dsl>验证 DSL</h2>
<p>默认的 Spiral 验证器接受以嵌套数组形式提供的验证规则。数组的键是要验证的属性名称，值是要依次应用于该属性值的验证规则数组：</p>
<pre><code class=language-php>$validator = $validation-&gt;validate(
    ['key' =&gt; null],
    [
        'key' =&gt; [
            'notEmpty', // 'key' 属性的值不能为空
            'string'    // 'key' 属性的值必须为字符串
        ]
    ]
);

if (!$validator-&gt;isValid()) {
    dump($validator-&gt;getErrors());
}
</code></pre>
<p>在这种情况下，规则是检查器（checker）方法或者任何可用的 PHP 函数，该方法或函数接受 <code>value</code> 作为第一个参数。</p>
<p>例如我们可以在验证规则中直接使用 <code>is_numeric</code></p>
<pre><code class=language-php>$validator = $validation-&gt;validate(
    ['key' =&gt; null],
    [
        'key' =&gt; [
            'notEmpty',  // 'key' 的值不能为空
            'is_numeric' // 'key' 的值必须是数字
        ]
    ]
);
</code></pre>
<h3 id=扩展声明>扩展声明</h3>
<p>在很多情况下，需要声明额外的规则参数、条件或者自定义错误信息。要实现这个目标，需要把规则声明封装为一个数组：</p>
<pre><code class=language-php>$validator = $validation-&gt;validate(
    ['key' =&gt; null],
    [
        'key' =&gt; [
            ['notEmpty'],  // 'key' 的值不能为空
            ['is_numeric'] // 'key' 的值必须是数组
        ]
    ]
);
</code></pre>
<blockquote>
<p>如果不需要额外的参数，可以省略用于包裹规则的 <code>[]</code>。</p>
</blockquote>
<h3 id=检查器规则>检查器规则</h3>
<p>规则名称可以用 <code>::</code> 前缀分隔，这种情况下第一部分是检查器的名称，第二部分是检查器的方法名称。例如：</p>
<pre><code class=language-php>$validator = $validation-&gt;validate(
    ['file' =&gt; null],
    [
        'file' =&gt; [
            'file::uploaded'
        ]
    ]
);
</code></pre>
<h3 id=参数>参数</h3>
<p>在规则数组中的所有值都会作为额外参数传递给检查方法，例如使用 <code>in_array</code> 方法做为规则的定义：</p>
<pre><code class=language-php>$validator = $validation-&gt;validate(
    ['name' =&gt; 'f'],
    [
        'name' =&gt; [
            'notEmpty',
            ['in_array', ['a', 'b', 'c'], true] // in_array($value, ['a', 'b', 'c'], true)
        ]
    ]
);
</code></pre>
<p>如果要指定正则表达式作为规则：</p>
<pre><code class=language-php>$validator = $validation-&gt;validate(
    ['name' =&gt; 'b'],
    [
        'name' =&gt; [
            'notEmpty',
            ['regexp', '/^a+$/'] // 'name' 的值必须是 a 开头的字符串
        ]
    ]
);
</code></pre>
<h3 id=错误信息>错误信息</h3>
<p>Validator will render default error message for any custom rule, to set custom error message set the rule
attribute:
验证器会为任何自定义规则提供默认的错误信息，但也可以自行设定错误信息，方法如下：</p>
<pre><code class=language-php>$validator = $validation-&gt;validate(
    ['file' =&gt; 'b'],
    [
        'file' =&gt; [
            'notEmpty',
            ['regexp', '/^a+$/', 'error' =&gt; '格式错误，应该为 &quot;a+&quot; 的格式。'] // aaa...
        ]
    ]
);
</code></pre>
<blockquote>
<p>你可以给任何规则制定自定义错误信息，不局限于自定义规则。</p>
</blockquote>
<h3 id=判断条件>判断条件</h3>
<p>有时，某条规则只有在另外的条件符合时才使用，这种情况下，可以为规则指定 <code>if</code> 属性：</p>
<pre><code class=language-php>$validator = $validation-&gt;validate(
    [
        'password'        =&gt; '',
        'confirmPassword' =&gt; ''
    ],
    [
        'password'        =&gt; [
            ['notEmpty']
        ],
        'confirmPassword' =&gt; [
            ['notEmpty', 'if' =&gt; ['withAll' =&gt; ['password']]]
        ]
    ]
);
</code></pre>
<blockquote>
<p>在上面的示例中，<code>confirmPassword</code> 属性的 <code>notEmpty</code> 规则只在 <code>password</code> 不为空时才触发。</p>
</blockquote>
<p>当然也可以使用多个条件，或者把它们组合成一个复杂的规则：</p>
<pre><code class=language-php> $validator = $validation-&gt;validate(
    [
        'password'        =&gt; 'abc',
        'confirmPassword' =&gt; 'cde'
    ],
    [
        'password'        =&gt; [
            ['notEmpty']
        ],
        'confirmPassword' =&gt; [
            ['notEmpty', 'if' =&gt; ['withAll' =&gt; ['password']]],
            ['match', 'password', 'error' =&gt; '两次输入的密码不一致']
        ]
    ]
);
</code></pre>
<h3 id=可用的条件>可用的条件</h3>
<p>以下是默认可用的判断条件：</p>
<table>
<thead>
<tr>
<th>名称</th>
<th>选项</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>withAny</td>
<td><em>array</em></td>
<td>当指定的任意一个属性不为空时。</td>
</tr>
<tr>
<td>withoutAny</td>
<td><em>array</em></td>
<td>当指定的任意一个属性为空时。</td>
</tr>
<tr>
<td>withAll</td>
<td><em>array</em></td>
<td>当指定的所有属性不为空时。</td>
</tr>
<tr>
<td>withoutAll</td>
<td><em>array</em></td>
<td>当指定的所有属性都为空时。</td>
</tr>
</tbody>
</table>
<blockquote>
<p>你可以用 <code>Spiral\Validation\ConditionInterface</code> 来创建自己的条件。</p>
</blockquote>
<h2 id=可用的规则>可用的规则</h2>
<p>以下是默认可用的验证规则。</p>
<blockquote>
<p>你可以用 <code>Spiral\Validation\AbstractChecker</code> 或者 <code>Spiral\Validation\CheckerInterface</code> 来创建自己的验证规则。</p>
</blockquote>
<h3 id=规则别名>规则别名</h3>
<p>最常用的规则集及其别名如下：</p>
<table>
<thead>
<tr>
<th>别名</th>
<th>规则</th>
</tr>
</thead>
<tbody>
<tr>
<td>notEmpty</td>
<td>type::notEmpty</td>
</tr>
<tr>
<td>required</td>
<td>type::notEmpty</td>
</tr>
<tr>
<td>datetime</td>
<td>datetime::valid</td>
</tr>
<tr>
<td>timezone</td>
<td>datetime::timezone</td>
</tr>
<tr>
<td>bool</td>
<td>type::boolean</td>
</tr>
<tr>
<td>boolean</td>
<td>type::boolean</td>
</tr>
<tr>
<td>cardNumber</td>
<td>mixed::cardNumber</td>
</tr>
<tr>
<td>regexp</td>
<td>string::regexp</td>
</tr>
<tr>
<td>email</td>
<td>address::email</td>
</tr>
<tr>
<td>url</td>
<td>address::url</td>
</tr>
<tr>
<td>file</td>
<td>file::exists</td>
</tr>
<tr>
<td>uploaded</td>
<td>file::uploaded</td>
</tr>
<tr>
<td>filesize</td>
<td>file::size</td>
</tr>
<tr>
<td>image</td>
<td>image::valid</td>
</tr>
<tr>
<td>array</td>
<td>is_array</td>
</tr>
<tr>
<td>callable</td>
<td>is_callable</td>
</tr>
<tr>
<td>double</td>
<td>is_double</td>
</tr>
<tr>
<td>float</td>
<td>is_float</td>
</tr>
<tr>
<td>int</td>
<td>is_int</td>
</tr>
<tr>
<td>integer</td>
<td>is_integer</td>
</tr>
<tr>
<td>numeric</td>
<td>is_numeric</td>
</tr>
<tr>
<td>long</td>
<td>is_long</td>
</tr>
<tr>
<td>null</td>
<td>is_null</td>
</tr>
<tr>
<td>object</td>
<td>is_object</td>
</tr>
<tr>
<td>real</td>
<td>is_real</td>
</tr>
<tr>
<td>resource</td>
<td>is_resource</td>
</tr>
<tr>
<td>scalar</td>
<td>is_scalar</td>
</tr>
<tr>
<td>string</td>
<td>is_string</td>
</tr>
<tr>
<td>match</td>
<td>mixed::match</td>
</tr>
</tbody>
</table>
<h3 id=类型>类型</h3>
<blockquote>
<p>前缀 <code>type::</code></p>
</blockquote>
<table>
<thead>
<tr>
<th>规则</th>
<th>参数</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>notEmpty</td>
<td>asString:<em>bool</em> - true</td>
<td>值不能为空（同 <code>!empty</code>）</td>
</tr>
<tr>
<td>notNull</td>
<td>&mdash;</td>
<td>值不能为 null</td>
</tr>
<tr>
<td>boolean</td>
<td>&mdash;</td>
<td>值必须为布尔值或数字[0,1]</td>
</tr>
</tbody>
</table>
<blockquote>
<p>以上的所有规则都可以省略前缀</p>
</blockquote>
<h3 id=必填项>必填项</h3>
<table>
<thead>
<tr>
<th>规则</th>
<th>参数</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>notEmpty</td>
<td>asString:<em>bool</em> - true</td>
<td>值不能为空</td>
</tr>
</tbody>
</table>
<p>例如：</p>
<pre><code class=language-php>class MyRequest extends \Spiral\Filters\Filter
{
    public const VALIDATES = [
        'name'  =&gt; [
            ['notEmpty'],
            ['my::abc']
        ]
    ];
}
</code></pre>
<h3 id=混合>混合</h3>
<blockquote>
<p>前缀 <code>mixed::</code></p>
</blockquote>
<table>
<thead>
<tr>
<th>规则</th>
<th>参数</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>cardNumber</td>
<td>&mdash;</td>
<td>通过 Luhn 算法检查信用卡卡号</td>
</tr>
<tr>
<td>match</td>
<td>field:<em>string</em>, strict:<em>bool</em> - false</td>
<td>检查值是否与另一个属性的值一致</td>
</tr>
</tbody>
</table>
<blockquote>
<p>上述的规则都可以用省略前缀。</p>
</blockquote>
<h3 id=地址>地址</h3>
<blockquote>
<p>前缀 <code>address::</code></p>
</blockquote>
<table>
<thead>
<tr>
<th>规则</th>
<th>参数</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>email</td>
<td>&mdash;</td>
<td>检查是否合法 Email</td>
</tr>
<tr>
<td>url</td>
<td>requireScheme:<em>bool</em> - true</td>
<td>检查是否合法 URL</td>
</tr>
</tbody>
</table>
<blockquote>
<p>上述的规则都可以用省略前缀。</p>
</blockquote>
<h2 id=数字>数字</h2>
<blockquote>
<p>前缀 <code>number::</code></p>
</blockquote>
<table>
<thead>
<tr>
<th>规则</th>
<th>参数</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>range</td>
<td>begin:<em>float</em>, end:<em>float</em></td>
<td>检查数字是否在指定范围</td>
</tr>
<tr>
<td>higher</td>
<td>limit:<em>float</em></td>
<td>检查数字是否大于或等于指定值</td>
</tr>
<tr>
<td>lower</td>
<td>limit:<em>float</em></td>
<td>检查数字是否小于或等于指定值</td>
</tr>
</tbody>
</table>
<h3 id=字符串>字符串</h3>
<blockquote>
<p>前缀 <code>string::</code></p>
</blockquote>
<table>
<thead>
<tr>
<th>规则</th>
<th>参数</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>regexp</td>
<td>expression:<em>string</em></td>
<td>检查字符串是否匹配指定正则表达式</td>
</tr>
<tr>
<td>shorter</td>
<td>length:<em>int</em></td>
<td>检查字符串长度是否小于或等于指定值</td>
</tr>
<tr>
<td>longer</td>
<td>length:<em>int</em></td>
<td>检查字符串长度是否大于或等于指定值</td>
</tr>
<tr>
<td>length</td>
<td>length:<em>int</em></td>
<td>检查字符串长度是否等于指定值</td>
</tr>
<tr>
<td>range</td>
<td>left:<em>int</em>, right:<em>int</em></td>
<td>检查字符串长度是否在指定范围内</td>
</tr>
</tbody>
</table>
<p>示例：</p>
<pre><code class=language-php>class MyRequest extends \Spiral\Filters\Filter
{
    public const VALIDATES = [
        'name' =&gt; [
            ['notEmpty'],
            ['string::length', 5]
        ]
    ];
}
</code></pre>
<h3 id=文件>文件</h3>
<blockquote>
<p>前缀 <code>file::</code></p>
</blockquote>
<p>文件检查器完全支持以字符串形式提供的文件名或者 PSR-7 的 <code>UploadedFileInterface</code> 接口：</p>
<table>
<thead>
<tr>
<th>规则</th>
<th>参数</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>exists</td>
<td>&mdash;</td>
<td>检查文件是否存在</td>
</tr>
<tr>
<td>uploaded</td>
<td>&mdash;</td>
<td>检查文件是否已上传</td>
</tr>
<tr>
<td>size</td>
<td>size:<em>int</em></td>
<td>检查文件尺寸是否小于指定值（单位为 KB）</td>
</tr>
<tr>
<td>extension</td>
<td>extensions:<em>array</em></td>
<td>检查文件扩展名是否在白名单内（使用客户端提供的文件名）</td>
</tr>
</tbody>
</table>
<h3 id=图片>图片</h3>
<blockquote>
<p>前缀 <code>image::</code></p>
</blockquote>
<p>图片检查器扩展了文件检查器，因此完全支持文件检查器的功能。</p>
<table>
<thead>
<tr>
<th>规则</th>
<th>参数</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>type</td>
<td>types:<em>array</em></td>
<td>检查图片类型是否在白名单内</td>
</tr>
<tr>
<td>valid</td>
<td>&mdash;</td>
<td>图片类型规则的别名（允许 JPEG, PNG, and GIF 格式）</td>
</tr>
<tr>
<td>smaller</td>
<td>width:<em>int</em>, height:<em>int</em></td>
<td>检查图片的像素尺寸是否小于指定值（高度可选）</td>
</tr>
<tr>
<td>bigger</td>
<td>width:<em>int</em>, height:<em>int</em></td>
<td>检查图片的像素尺寸是否大于指定值（高度可选）</td>
</tr>
</tbody>
</table>
<h3 id=日期时间>日期时间</h3>
<blockquote>
<p>前缀 <code>datetime::</code></p>
</blockquote>
<table>
<thead>
<tr>
<th>规则</th>
<th>参数</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>future</td>
<td>orNow:<em>bool</em> - false,<br>useMicroSeconds:<em>bool</em> - false</td>
<td>必须为未来的时间</td>
</tr>
<tr>
<td>past</td>
<td>orNow:<em>bool</em> - false,<br>useMicroSeconds:<em>bool</em> - false</td>
<td>必须为过去的时间</td>
</tr>
<tr>
<td>format</td>
<td>format:<em>string</em></td>
<td>值必须符合指定的时间格式</td>
</tr>
<tr>
<td>before</td>
<td>field:<em>string</em>,<br>orEquals:<em>bool</em> - false,<br>useMicroSeconds:<em>bool</em> - false</td>
<td>值必须为指定值之前的时间</td>
</tr>
<tr>
<td>after</td>
<td>field:<em>string</em>,<br>orEquals:<em>bool</em> - false,<br>useMicroSeconds:<em>bool</em> - false</td>
<td>值必须为指定值之后的时间</td>
</tr>
<tr>
<td>valid</td>
<td>&mdash;</td>
<td>值必须为有效的时间日期定义，包括数字时间戳</td>
</tr>
<tr>
<td>timezone</td>
<td>&mdash;</td>
<td>值必须为有效的时区定义</td>
</tr>
</tbody>
</table>
<blockquote>
<p>设置 <code>useMicroSeconds</code> 为 true 允许检查含有微妙的时间。<br>
要注意的是，两个 <code>new \DateTime('now')</code> 创建的对象 99% 会包含不同的微秒值，因此它们总是不相等。</p>
</blockquote>
<h3 id=实体>实体</h3>
<blockquote>
<p>前缀 <code>entity::</code></p>
</blockquote>
<p>Cycle ORM 专属的检查器。</p>
<table>
<thead>
<tr>
<th>规则</th>
<th>参数</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>exists</td>
<td>class:<em>string</em>, field:<em>string</em> - null</td>
<td>实体在数据库中是否存在。<code>class</code> 是一个实体的类名。<code>field</code> 是属性对应的列。</td>
</tr>
<tr>
<td>unique</td>
<td>class:<em>string</em>, field:<em>string</em>, withFields:<em>string[]</em></td>
<td>值在数据库中必须是唯一的。<code>class</code> 和 <code>field</code> 定义同上，<code>withFields</code> 可以指定其它的字段，与输入值共同用于检查是否唯一。</td>
</tr>
</tbody>
</table>
<p>示例：</p>
<h4 id=exists>exists:</h4>
<pre><code class=language-php>class MyRequest extends \Spiral\Filters\Filter
{
    public const VALIDATES = [
        'id' =&gt; [
            ['entity::exists', \App\Database\User::class]
        ]
    ];
}
</code></pre>
<blockquote>
<p>检查指定 id 的用户在数据库中是否存在</p>
</blockquote>
<pre><code class=language-php>class MyRequest extends \Spiral\Filters\Filter
{
    public const VALIDATES = [
        'email' =&gt; [
            ['entity::exists', \App\Database\User::class, 'email']
        ]
    ];
}
</code></pre>
<blockquote>
<p>检查指定 email 的用户在数据库中是否存在</p>
</blockquote>
<h4 id=unique>unique</h4>
<p>使用该规则时，可以传递一个实体对象作为上下文对象。如果上下文中存在该对象，它会做为不可变值来计算，检查器会返回 true。否则检查器会在数据库中进行查找。</p>
<pre><code class=language-php>class MyRequest extends \Spiral\Filters\Filter
{
    public const VALIDATES = [
        'email' =&gt; [
            ['entity::unique', \App\Database\User::class, 'email', ['company']]
        ]
    ];
}
</code></pre>
<blockquote>
<p>检查指定 email 以及 company 的组合在用户表中的记录是否唯一。</p>
</blockquote>
<p>通过验证器上下文，你可以传递当前的值，这样它们不会与数据库中的当前实体发生冲突。</p>
<pre><code class=language-php>/** @var \App\Database\User $user */
$request-&gt;setContext($user);
</code></pre>
<h2 id=自定义验证规则>自定义验证规则</h2>
<p>可以通过自定义检查器的实现来创建特定于应用程序的验证规则。</p>
<pre><code class=language-php>namespace App\Security;

use Spiral\Database\Database;
use Spiral\Validation\AbstractChecker;

class DBChecker extends AbstractChecker
{
    public const MESSAGES = [
        'user' =&gt; '用户不存在'
    ];

    /** @var Database */
    private $db;

    /**
     * @param Database $db
     */
    public function __construct(Database $db)
    {
        $this-&gt;db = $db;
    }

    /**
     * @param $id
     * @return bool
     */
    public function user($id): bool
    {
        return $this-&gt;db-&gt;table('users')-&gt;select()-&gt;where('id', $id)-&gt;count() === 1;
    }
}
</code></pre>
<blockquote>
<p>使用预建常量 <code>MESSAGE</code> 来定义自定义错误模板。</p>
</blockquote>
<p>要激活自定义的检查器，需要在 <code>ValidationBootloader</code> 中注册它：</p>
<pre><code class=language-php>namespace App\Bootloader;

use App\Security\DBChecker;
use Spiral\Boot\Bootloader\Bootloader;
use Spiral\Bootloader\Security\ValidationBootloader;

class CheckerBootloader extends Bootloader
{
    public function boot(ValidationBootloader $validation)
    {
        $validation-&gt;addChecker('db', DBChecker::class);
    }
}
</code></pre>
<p>完成上述操作后，即可通过 <code>db::user</code> 使用该自定义的规则。</p>
</div>
<div class=article-widget>
<div class=post-nav>
<div class=post-nav-item>
<div class=meta-nav>上一页</div>
<a href=/docs/filters/configuration/ rel=next>安装和配置</a>
</div>
<div class=post-nav-item>
<div class=meta-nav>下一页</div>
<a href=/docs/cycle/configuration/ rel=prev>Configuration</a>
</div>
</div>
</div>
</div>
<div class=body-footer>
<p>最近更新于 2020-08-28</p>
<p class=edit-page>
<a href=https://github.com/krwu/spiraldocs/edit/feat/chinese/zh_CN/security/validation.md>
<i class="fas fa-pen pr-2"></i>编辑本页
</a>
</p>
</div>
</article>
<footer class=site-footer>
<p class=powered-by><span class=copyright>© 2019 - 2021 <a href=https://studyspiral.cn/>StudySpiral</a> All rights reserved.</span>
</p>
<p class=powered-by>
<a href=http://www.beian.miit.gov.cn/ target=_blank rel="noopener noreferer">粤ICP备14011364号</a>
<a href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=44030402001866" target=_blank rel="noopener noreferer"><img style=display:inline-block src=/img/gaba.png>粤公网安备 44030402001866号</a>
</p>
<p id=webify class=powered-by style=display:flex><span style="margin:0 .2rem 0 0;display:block;height:20px;line-height:20px">Powered by</span> <a href=https://webify.cloudbase.net/ target=_blank rel=noopener>CloudBase Webify</a></p>
<script async defer>window.addEventListener('DOMContentLoaded',()=>{const{host:a}=window.location;(a.toLocaleLowerCase()==='docs.studyspiral.cn'||a.indexOf(".app.tcloudbase.com"))&&(document.getElementById('webify').style.display='flex')})</script>
</footer>
</main>
</div>
</div>
<script src=https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin=anonymous></script>
<script src=https://cdnjs.cloudflare.com/ajax/libs/jquery.imagesloaded/4.1.4/imagesloaded.pkgd.min.js integrity="sha256-lqvxZrPLtfffUl2G/e7szqSvPBILGbwmsGE1MKlOi0Q=" crossorigin=anonymous></script>
<script src=https://cdnjs.cloudflare.com/ajax/libs/jquery.isotope/3.0.6/isotope.pkgd.min.js integrity="sha256-CBrpuqrMhXwcLLUd5tvQ4euBHCdh7wGlDfNz8vbu/iI=" crossorigin=anonymous></script>
<script src=https://cdnjs.cloudflare.com/ajax/libs/fancybox/3.5.7/jquery.fancybox.min.js integrity="sha256-yt2kYMy0w8AbtF89WXb2P1rfjcP/HTHLT7097U8Y5b8=" crossorigin=anonymous></script>
<script src=https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.1/highlight.min.js integrity="sha256-eOgo0OtLL4cdq7RdwRUiGKLX9XsIJ7nGhWEKbohmVAQ=" crossorigin=anonymous></script>
<script src=https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.1/languages/go.min.js></script>
<script>const code_highlighting=!0</script>
<script>const isSiteThemeDark=!1</script>
<script>const search_config={indexURI:"/index.json",minLength:1,threshold:.3},i18n={no_results:"没有找到结果",placeholder:"搜索...",results:"搜索结果"},content_type={post:"文章",project:"项目",publication:"出版物",talk:"演讲"}</script>
<script src=https://cdnjs.cloudflare.com/ajax/libs/anchor-js/4.1.1/anchor.min.js integrity="sha256-pB/deHc9CGfFpJRjC43imB29Rse8tak+5eXqntO94ck=" crossorigin=anonymous></script>
<script>anchors.add()</script>
<script id=search-hit-fuse-template type=text/x-template>
      <div class="search-hit" id="summary-{{key}}">
      <div class="search-hit-content">
        <div class="search-hit-name">
          <a href="{{relpermalink}}">{{title}}</a>
          <div class="article-metadata search-hit-type">{{type}}</div>
          <p class="search-hit-description">{{snippet}}</p>
        </div>
      </div>
      </div>
    </script>
<script src=https://cdnjs.cloudflare.com/ajax/libs/fuse.js/3.2.1/fuse.min.js integrity="sha256-VzgmKYmhsGNNN4Ph1kMW+BjoYJM2jV5i4IlFoeZA9XI=" crossorigin=anonymous></script>
<script src=https://cdnjs.cloudflare.com/ajax/libs/mark.js/8.11.1/jquery.mark.min.js integrity="sha256-4HLtjeVgH0eIB3aZ9mLYF6E8oU5chNdjU6p6rrXpl9U=" crossorigin=anonymous></script>
<script src=/js/academic.min.6f1005d1a84220e2f466ff3d8e712f31.js></script>
<div id=modal class="modal fade" role=dialog>
<div class=modal-dialog>
<div class=modal-content>
<div class=modal-header>
<h5 class=modal-title>引用</h5>
<button type=button class=close data-dismiss=modal aria-label=Close>
<span aria-hidden=true>&#215;</span>
</button>
</div>
<div class=modal-body>
<pre><code class="tex hljs"></code></pre>
</div>
<div class=modal-footer>
<a class="btn btn-outline-primary my-1 js-copy-cite" href=# target=_blank>
<i class="fas fa-copy"></i> 复制
</a>
<a class="btn btn-outline-primary my-1 js-download-cite" href=# target=_blank>
<i class="fas fa-download"></i> 下载
</a>
<div id=modal-error></div>
</div>
</div>
</div>
</div>
</body>
</html>